<!--
* @Description SVGIcon svg组件
* @author 三咲问道
* @date 2025/6/6
-->
<template>
  <div class="SVGIcon">
    <component
        :is="svg"
        :id="randomId"
        v-bind="$attrs"
        :class="['svg-icon']"
        :style="{
        '--svg-opacity': opacity,
        '--svg-size': size && !isNaN(Number(size)) ? `${size}px` : size
      }"
    />
  </div>
</template>

<script setup lang="ts">
import {computed, defineAsyncComponent, watch, onUnmounted, watchEffect, ref} from 'vue';

const props = defineProps({
  name: {
    type: String,
    required: true
  }, // 文件名
  size: {
    type: [String, Number], // 数字默认单位补px
  }, // 大小
  color: {
    type: String,
  }, // 颜色
  opacity: {
    type: [String, Number],
  } // 透明度
});

// svg元素
const svgEl = ref<HTMLElement | null>(null)
/**
 * 生成随机字符串
 * @returns 随机字符串
 */
const randomString = () => {
  return Math.random().toString(36).substring(2, 15)
}

const randomId = `svg-icon-${randomString()}` // 随机id
let renderTimer: any | null = null // 渲染定时器

// 获取svg文件作为组件
const svg = computed(() => {
  return defineAsyncComponent(() => import(`../../assets/svgIcon/${props.name}.svg`)) || null
})

/**
 * 当 SVG 文件被动态导入时，Vue 可能会对其进行解析和处理，导致某些属性被重写或修改 这里元素viewBox/id会受到影响变为a, id与其他重复导致渲染失败
 * 初始化元素处理
 */
const initElement = () => {
  const svgElement = document.getElementById(randomId)
  svgEl.value = svgElement
  if (!svgElement) return
  clearTimeout(renderTimer)

  // 判断svg是否有viewBox属性, 没有则按宽高设置
  const viewBox = svgElement.getAttribute('viewBox')
  const width = svgElement.getAttribute('width')
  const height = svgElement.getAttribute('height')
  if (!viewBox && width && height) {
    svgElement.setAttribute('viewBox', `0 0 ${width} ${height}`)
  }

  // id处理
  const idElements = svgElement.querySelectorAll('[id]') || []
  idElements?.forEach(element => {
    const new_id = randomString()
    // 替换svg中的fill="url(#id)" 或者 xlink:href="#id" 新的id引用
    const fillUseElements = (svgElement.querySelectorAll(`[fill="url(#${element.id})"]`) || [])
    const clipPathUseElements = (svgElement.querySelectorAll(`[clip-path="url(#${element.id})"]`) || [])
    const maskUseElements = (svgElement.querySelectorAll(`[mask="url(#${element.id})"]`) || [])
    const xlinkUseElements = Array.from(svgElement.querySelectorAll(`use`)).filter(el => el.href.baseVal === `#${element.id}`)
    if (fillUseElements.length > 0 || clipPathUseElements.length > 0 || maskUseElements.length > 0 || xlinkUseElements.length > 0) {
      fillUseElements.forEach(useElement => {
        useElement.setAttribute('fill', `url(#${new_id})`)
      })
      clipPathUseElements.forEach(useElement => {
        useElement.setAttribute('clip-path', `url(#${new_id})`)
      })
      xlinkUseElements.forEach(useElement => {
        useElement.setAttribute('href', `#${new_id}`)
      })
      maskUseElements.forEach(useElement => {
        useElement.setAttribute('mask', `url(#${new_id})`)
      })
      element.id = new_id
    }
  })
}

// svg组件获取时，做一些初始化处理
watch(svg, () => {
  if (renderTimer) clearTimeout(renderTimer)
  renderTimer = setInterval(initElement, 100)
}, {
  immediate: true
})

// 监听type=auto时, 色值变化
watchEffect(() => {
  if (!svgEl.value) return
  // type==auto时 自动判断-有fill属性时设置fill 有stroke属性时设置stroke
  if (props.color) {
    const val = props.color
    // 色值设置在svg上
    const pFill = svgEl.value.getAttribute('fill')
    const pStroke = svgEl.value.getAttribute('stroke')
    if (pStroke) {
      svgEl.value.setAttribute('stroke', val)
    } else if (pFill) {
      svgEl.value.setAttribute('fill', val)
    }
    // 色值设置在子节点上
    const attrElements = ['path', 'circle', 'rect', 'ellipse', 'line', 'polyline', 'polygon']
    const svgChildElements: any[] = []
    attrElements.forEach(attrElement => {
      const pathElements = svgEl.value?.querySelectorAll(attrElement) || []
      pathElements.forEach(el => {
        svgChildElements.push(el)
      })
    })
    svgChildElements.forEach(pathElement => {
      const fill = pathElement.getAttribute('fill')
      const stroke = pathElement.getAttribute('stroke')
      if (stroke) {
        pathElement.setAttribute('stroke', val)
      } else if (fill) {
        pathElement.setAttribute('fill', val)
      }
    })
  }
})

onUnmounted(() => {
  if (renderTimer) clearTimeout(renderTimer)
})


</script>


<style scoped lang="less">
.SVGIcon {
  .svg-icon {
    outline: none;
    width: var(--svg-size);
    height: var(--svg-size);

    :deep(path) {
      fill-opacity: var(--svg-opacity);
    }
  }
}
</style>